﻿using System;
using System.IO;
using System.Reflection;

using FireBus.Contracts;


namespace FireBus.Isolation
{
	/// <remarks>
	/// This class can be instantiated only once per AppDomain.
	/// </remarks>
	public class ExtensionController : MarshalByRefObject
	{
		private ExtensionAdapterBuilder _ExtensionAdapter;

		/// <remarks>
		/// Making this member instance can cause to AccessViolationException.
		/// </remarks>
		private static ExtensionAccessorAdapter _ExtensionAccessor;


		public ExtensionController()
		{
			AppDomain.CurrentDomain.AssemblyResolve += OnCurrentDomainAssemblyResolve;
		}


		private static Assembly OnCurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
		{
			foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
			{
				if (assembly.FullName == args.Name)
				{
					return assembly;
				}
			}
			return null;
		}

		public void Initialize(
			ExtensionName extensionName,
			String absolutePath,
			TypeName extensionTypeName,
			ExtensionAccessorAdapter extensionAccessor)
		{
			if (extensionName == null)
			{
				throw new ArgumentNullException("extensionName");
			}
			if (String.IsNullOrEmpty(absolutePath))
			{
				throw new ArgumentNullException("absolutePath");
			}
			if (extensionTypeName == null)
			{
				throw new ArgumentNullException("extensionTypeName");
			}
			if (extensionAccessor == null)
			{
				throw new ArgumentNullException("extensionAccessor");
			}
			_ExtensionAccessor = extensionAccessor;
			PreloadAssemblies(absolutePath);
			_ExtensionAdapter = CreateExtension(extensionName, extensionTypeName);

			AppDomain.CurrentDomain.AssemblyResolve -= OnCurrentDomainAssemblyResolve;
		}

		private static void PreloadAssemblies(String absolutePath)
		{
			foreach (var file in Directory.GetFiles(absolutePath, "*.dll"))
			{
				try
				{
					Assembly.Load(
						AssemblyName.GetAssemblyName(file));
				}
				catch(BadImageFormatException)
				{
				}
				catch(FileNotFoundException)
				{
				}
				catch(FileLoadException)
				{
				}
			}
		}

		private static ExtensionAdapterBuilder CreateExtension(ExtensionName extensionName, TypeName typeName)
		{
			var adapter = new ExtensionAdapterBuilder(extensionName, typeName);
			return adapter;
		}


		public ExtensionAdapter GetInstance()
		{
			return _ExtensionAdapter.Adapter;
		}

		public static void NotifyAll<TEvent>(TEvent args)
			where TEvent : class
		{
			if (args == null)
			{
				throw new ArgumentNullException("args");
			}
			
			var finder = new EventListenerFinder(_ExtensionAccessor.GetEventListeners);
			var asyncResult = finder.BeginInvoke(typeof(TEvent), null, null);
			var extensions = finder.EndInvoke(asyncResult);
			foreach (var extension in extensions)
			{
				var listener = extension as IEventListener<TEvent>;
				if (listener == null)
				{
					continue;
				}
				//converting this to method group can cause to TypeLoadException
				var notifier = new Notifier<TEvent>(
					(e) => listener.OnEvent(e));
				notifier.BeginInvoke(args, null, null);
			}
		}

		private delegate Object[] EventListenerFinder(TypeName eventTypeName);

		private delegate void Notifier<TEvent>(TEvent args);

		public static void Send<TRequest, TResponse>(SendEventArgs<TRequest, TResponse> eventArgs)
			where TRequest : class
			where TResponse : class
		{
			if (eventArgs == null)
			{
				throw new ArgumentNullException("eventArgs");
			}
			
			var finder = new RequestListenerFinder(_ExtensionAccessor.GetRequestListeners);
			var asyncResult = finder.BeginInvoke(typeof(TRequest), typeof(TResponse), null, null);
			var extensions = finder.EndInvoke(asyncResult);
			
			foreach (var extension in extensions)
			{
				var adapter = extension as ExtensionAdapter;
				if (adapter == null)
				{
					continue;
				}
				if (!eventArgs.IsMulticast &&
					eventArgs.Listener != null)
				{
					if (!eventArgs.Listener.Equals(adapter.Metadata.Name))
					{
						continue;
					}
				}
				var listener = extension as IRequestListener<TRequest, TResponse>;
				if (listener == null)
				{
					continue;
				}
				//converting this to method group can cause to TypeLoadException
				var notifier = new Sender<TRequest, TResponse>(
					(r) => listener.OnRequest(r));
				asyncResult = notifier.BeginInvoke(eventArgs.Request, null, null);
				var response = notifier.EndInvoke(asyncResult);
				if (response != null)
				{
					eventArgs.Responses.Add(
						adapter.Metadata.Name,
						response);
				}

				if (!eventArgs.IsMulticast)
				{
					break;
				}
			}
		}

		private delegate Object[] RequestListenerFinder(TypeName requestTypeName, TypeName responseTypeName);

		private delegate TResponse Sender<TRequest, TResponse>(TRequest request);

		public override Object InitializeLifetimeService()
		{
			return null;
		}
	}
}
